package ncse.bpNeuralNetworks;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class BpNeuralNetworks {
	private static double ETA = 0.7;
	private static int N = 4;
	private static int SIZE = N * N;
	private static int MAXVAL =  Short.MAX_VALUE;
	@SuppressWarnings("unused")
	private static int MINVAL =  Short.MIN_VALUE;
	private double input_layer[];
	private double competition_layer[];
	private double output_layer;

	private double y_i;
	private double w_ji[];
	private double w_kj[][];
	private double y_j[];

	public BpNeuralNetworks() {
		// TODO Auto-generated constructor stub
		input_layer = new double[SIZE];
		competition_layer = new double[SIZE];

		w_ji = new double[SIZE];
		w_kj = new double[SIZE][SIZE];
		y_j = new double[SIZE];

		init_net();
	}

	private void init_net() {
		for (int i = 0; i < SIZE; i++) {
			input_layer[i] = 0.0;
			competition_layer[i] = 0.0;
		}
		output_layer = 0.0;
	}
	// 初始化权重
	public void init_weight() {
		for (int i = 0; i < SIZE; i++) {
			w_ji[i] = Math.random();
		}
		for (int i = 0; i < SIZE; i++) {
			for (int j = 0; j < SIZE; j++) {
				w_kj[i][j] = Math.random();
			}
		}
	}

	// 激活函数。采用双曲正切函数。
	// 输入：整合函数值（整合函数为加权求和）。
	// 返回值：激活函数值。
	private double logistic_s(double theta) {
		return (1.0 - Math.exp(-theta)) / (1.0 + Math.exp(theta));
	}

	// BP神经网络应用函数。输入棋盘，立即得到其倒推值。
	// 输入：当前棋盘状态。
	// 返回值：神经网络输出值（需要乘以MAX才等于倒推值）。
	public double BP_net_value(int[] chess_board) {
		init_net();
		for (int i = 0; i < SIZE; i++) {
			input_layer[i] = (double) chess_board[i];
		}
		for (int j = 0; j < SIZE; j++) {
			for (int k = 0; k < SIZE; k++) {
				competition_layer[j] += w_kj[k][j] * input_layer[k];
			}
		}
		for (int j = 0; j < SIZE; j++) {
			output_layer += w_ji[j] * logistic_s(competition_layer[j]);
		}
		y_i = logistic_s(output_layer);

		return y_i;
	}

	// 调整权值函数。进行不超过最大迭代次数的正向/反向传播过程，不断调整权值。如果误差小于允许值则结束。
	// 输入：一组学习样本（代表棋盘的16维向量以及对应倒推值）。
	// 返回值：void
	public void adjust_BP_net_weight(int[] chess_board, int backed_up_value) {
		int iteration_times = 1000000;
		int temp_iteration_times = 0;

		while (true) {
			y_i = BP_net_value(chess_board);
			if (y_i == backed_up_value) return;
			// 如果达到允许的最小误差或最大迭代次数，退出并结束本次学习
			if (Math.abs(y_i - (double)backed_up_value / (double)MAXVAL) < 0.000001
					|| temp_iteration_times >= iteration_times) {
				break;
			}

			// 开始反向传播
			for (int j = 0; j < SIZE; j++) {
				w_ji[j] = w_ji[j] - ETA * y_j[j] * (1 - y_i * y_i)
						* (y_i - (double) backed_up_value / (double) MAXVAL);
			}
			for (int k = 0; k < N * N; k++) {
				for (int j = 0; j < N; j++) {
					w_kj[k][j] = w_kj[k][j]
							- ETA
							* input_layer[k]
							* (1 - y_j[j] * y_j[j])
							* w_ji[j]
							* (1 - y_i * y_i)
							* (y_i - (double) backed_up_value / (double) MAXVAL);
				}
			}
			temp_iteration_times++;
		}
		return;
	}

	public void read_weight() {
		String num[];
		try {
			String encoding = "utf-8";
			File file = new File("./res/BP_net_weight.txt");
			if (file.isFile() && file.exists()) { // 判断文件是否存在
				InputStreamReader read = new InputStreamReader(
						new FileInputStream(file), encoding);// 考虑到编码格式
				BufferedReader bufferedReader = new BufferedReader(read);
				String lineTxt = null;
				lineTxt = bufferedReader.readLine();
				num = lineTxt.split(" ");
				for (int i = 0; i < SIZE; i++) {
					w_ji[i] = Double.parseDouble(num[i]);
				}
				lineTxt = bufferedReader.readLine();
				num = lineTxt.split(" ");
				for (int i = 0; i < SIZE; i++) {
					for (int j = 0; j < SIZE; j++) {
						w_kj[i][j] = Double.parseDouble(num[i * SIZE + j]);
					}
				}
				read.close();
			} else {
				System.out.println("找不到指定的文件");
			}
		} catch (Exception e) {
			System.out.println("读取文件内容出错");
			e.printStackTrace();
		}
	}
	
	public void write_mode() {
		try {
			File file = new File("./res/BP_net_weight.txt");
			file.createNewFile();
			OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream(file), "utf-8");
			BufferedWriter bwrite = new BufferedWriter(ow);
			for (int i = 0; i < SIZE; i++) {
				bwrite.write(w_ji[i] + " ");
			}
			bwrite.write("\n");
			for (int i = 0; i < SIZE; i++) {
				for (int j = 0; j < SIZE; j++) {
					bwrite.write(w_kj[i][j] + " ");
				}
			}
			bwrite.close();
		} catch (Exception e) {
			
		}
	}
}
